#pragma warning(push)
#pragma warning(disable:4996)

#include <stdlib.h>
#include <tchar.h>

#include "image.h"
#include "helpers.h"
#include "myerrors.h"

extern "C" {
#include "libmng.h"
#include "libmng_data.h"
#include "libmng_error.h"

#include "commander.h"
}


//////////////////////////////////////////////////////////////////////////
//string constants
const TCHAR* strMsgStarting = _T("Parsing file ");
const TCHAR* strMsgFinished = _T("Success!");

//////////////////////////////////////////////////////////////////////////

struct mng_info
{
	~mng_info() {delete image;}

	FILE* file;

	mng_uint32       width;
	mng_uint32       height;
	mng_uint8        colortype;
	mng_uint8        imagesampledepth;
	mng_uint8        imagecompression;
	mng_uint8        imageinterlace;
	mng_uint8        alphasampledepth;
	mng_uint8        alphacompression;
	mng_uint8        alphafilter;
	mng_uint8        alphainterlace;

	mng_uint32      timer;
	Image*			image;

	int				chunkSeq;
	const TCHAR*	chunksOutDir;
};

static mng_ptr mymngalloc(mng_uint32 size)
{
	return (mng_ptr)calloc(1, size);
}

static void mymngfree(mng_ptr p, mng_uint32 size)
{
	free(p);
}

static mng_bool mymngerror(mng_handle mng, mng_int32 code, mng_int8 severity, mng_chunkid chunktype, mng_uint32 chunkseq, mng_int32 extra1, mng_int32 extra2, mng_pchar text)
{
	return mng_cleanup(&mng); //<Arkadiy Olovyannikov>
}

static mng_bool mymngopenstream(mng_handle mng)      { return MNG_TRUE; }
static mng_bool mymngclosestream(mng_handle mng)     { return MNG_TRUE; }

static mng_bool mymngreadstream(mng_handle mng, mng_ptr buffer, mng_uint32 size, mng_uint32 *bytesread)
{
	mng_info *mymng = (mng_info*)mng_get_userdata(mng);
	*bytesread = fread(buffer, 1, size, mymng->file);

	return MNG_TRUE;
}

TCHAR* make_chunk_file_name(const TCHAR* outDir, const TCHAR* chunkName, int chunkSeq)
{
	TCHAR* fullName;
	TCHAR  seqString[16] = _T("");
	_itot(chunkSeq, seqString, 10);

	size_t fullNameLen = _tcslen(outDir) + _tcslen(chunkName) + _tcslen(seqString) + _tcslen(_T(".chunk")) + 2;
	fullName = new TCHAR[fullNameLen];
	if(!fullName)
		return NULL;

	_tcscpy(fullName, outDir);
	_tcscat(fullName, chunkName);
	_tcscat(fullName, _T("-"));
	_tcscat(fullName, seqString);
	_tcscat(fullName, _T(".chunk"));

	return fullName;
}

bool dump_chunk_data(const TCHAR* outDir, const TCHAR* chunkName, int chunkSeq, void* data, size_t dataSize)
{
	TCHAR* fileName = make_chunk_file_name(outDir, chunkName, chunkSeq);

	FILE* file = _tfopen(fileName, _T("wb"));
	delete[] fileName;
	if(!file)
	{
		return false;
	}

	size_t writeRes = fwrite(data, 1, dataSize, file);

	fclose(file);

	return writeRes == dataSize;
	
}

mng_bool process_idat(mng_handle hHandle, mng_handle hChunk)
{
	mng_info *mymng = (mng_info*)mng_get_userdata(hHandle);

	mng_uint32      rawlen;
	mng_ptr         rawdata;

	if(mng_getchunk_idat(hHandle, hChunk, &rawlen, &rawdata ) != 0)
		return false;

	return dump_chunk_data(mymng->chunksOutDir, _T("IDAT"), mymng->chunkSeq, rawdata, rawlen);
}

mng_bool process_jhdr(mng_handle hHandle, mng_handle hChunk)
{
	mng_info *mymng = (mng_info*)mng_get_userdata(hHandle);

	if(mng_getchunk_jhdr(hHandle, hChunk, &mymng->width, &mymng->height, &mymng->colortype, &mymng->imagesampledepth,
						&mymng->imagecompression, &mymng->imageinterlace, &mymng->alphasampledepth, &mymng->alphacompression,
						&mymng->alphafilter, &mymng->alphainterlace) != 0)
		return false;

	TCHAR* fileName = make_chunk_file_name(mymng->chunksOutDir, _T("JHDR"), mymng->chunkSeq);
	FILE* file = _tfopen(fileName, _T("wb"));
	delete[] fileName;
	if(!file)
	{
		return false;
	}

	fprintf(file, "width:%d\r\n", mymng->width);
	fprintf(file, "height:%d\r\n", mymng->height);
	fprintf(file, "colortype:%d\r\n", mymng->colortype);
	fprintf(file, "sample depth:%d\r\n", mymng->imagesampledepth);
	fprintf(file, "compression:%d\r\n", mymng->imagecompression);
	fprintf(file, "interlace:%d\r\n", mymng->imageinterlace);
	fprintf(file, "alpha sample depth:%d\r\n", mymng->alphasampledepth);
	fprintf(file, "alpha compression:%d\r\n", mymng->alphacompression);
	fprintf(file, "alpha filter:%d\r\n", mymng->alphafilter);
	fprintf(file, "alpha interlace:%d\r\n", mymng->alphainterlace);

	fclose(file);
	return true;
}

mng_bool process_jdaa(mng_handle hHandle, mng_handle hChunk)
{
	mng_info *mymng = (mng_info*)mng_get_userdata(hHandle);

	mng_uint32      rawlen;
	mng_ptr         rawdata;

	if( mng_getchunk_jdaa(hHandle, hChunk, &rawlen, &rawdata) != 0 )
		return false;

	return dump_chunk_data(mymng->chunksOutDir, _T("JDAA"), mymng->chunkSeq, rawdata, rawlen);
}

mng_bool process_jdat(mng_handle hHandle, mng_handle hChunk)
{
	mng_info *mymng = (mng_info*)mng_get_userdata(hHandle);

	mng_uint32      rawlen;
	mng_ptr         rawdata;

	if( mng_getchunk_jdat(hHandle, hChunk, &rawlen, &rawdata) != 0 )
		return false;

	return dump_chunk_data(mymng->chunksOutDir, _T("JDAA"), mymng->chunkSeq, rawdata, rawlen);
}

mng_bool mymng_iteratechunk(mng_handle hHandle, mng_handle hChunk, mng_chunkid iChunkid, mng_uint32 iChunkseq)
{
	mng_info* mngInfo = (mng_info*)mng_get_userdata (hHandle);

	switch(iChunkid)
	{
	case  MNG_UINT_IDAT:
		if(!process_idat(hHandle, hChunk))
			return false;
		break;
	case  MNG_UINT_JDAA:
		if( !process_jdaa(hHandle, hChunk))
			return false;
		break;
	case  MNG_UINT_JDAT:
		if( !process_jdat(hHandle, hChunk))
			return false;
		break;
	case  MNG_UINT_JHDR:
		if(!process_jhdr(hHandle, hChunk))
			return false;
		break;
	}

	mngInfo->chunkSeq++;

	return true;
}

mng_bool mymngprocessheader (mng_handle hHandle, mng_uint32 iWidth, mng_uint32 iHeight)
{

	mng_info* mngInfo = (mng_info*)mng_get_userdata (hHandle);

	if (mng_set_canvasstyle (hHandle, MNG_CANVAS_RGBA8))
	{
		showErrorAndExit(strErrDecodeInputFile, ERROR_DECODE_INPUT_FILE);
		return MNG_FALSE;
	}

	mngInfo->width = iWidth;
	mngInfo->height = iHeight;

	mngInfo->image = Image::create(iWidth, iHeight, Image::FORMAT_RGBA, 8);

	return MNG_TRUE;
}

mng_ptr mymnggetcanvasline(mng_handle hHandle, mng_uint32 iLinenr)
{
	mng_info* mngInfo = (mng_info*)mng_get_userdata (hHandle);

	return ((unsigned char*)mngInfo->image->getPixels()) + mngInfo->image->getPitch() * iLinenr;
}

mng_uint32 mymnggettickcount (mng_handle hHandle)
{
	mng_info* mngInfo = (mng_info*)mng_get_userdata (hHandle);

	return 0;      
}

mng_bool mymngsettimer (mng_handle hHandle, mng_uint32 iMsecs)
{
	mng_info* mngInfo = (mng_info*)mng_get_userdata (hHandle);

	mngInfo->timer = iMsecs;

	return MNG_TRUE;
}

mng_bool mymngimagerefresh (mng_handle hHandle,
		mng_uint32 iX, mng_uint32 iY, 
		mng_uint32 iWidth, mng_uint32 iHeight)
{
	return MNG_TRUE;
}

void loadJNG(const TCHAR* jngFileName, Image* &outImage, const TCHAR* chunksOutDir)
{
	const char magicJNG[] = "\x8bJNG\x0d\x0a\x1a\x0a";
	char magicBuff[sizeof(magicJNG)];

	FILE* inFile = NULL;
	outImage = NULL;

	inFile = _tfopen(jngFileName, _T("rb"));
	if(inFile==NULL)
	{
		showErrorAndExit(strErrOpengInputFile, ERROR_OPEN_INPUT_FILE);
	}

	size_t readRes = fread(magicBuff, 1, sizeof(magicJNG), inFile);
	if(readRes!=sizeof(magicJNG))
	{
		fclose(inFile);
		showErrorAndExit(strErrDecodeInputFile, ERROR_DECODE_INPUT_FILE);
	}

	fseek(inFile, 0, SEEK_SET);

	if(memcmp(magicJNG, magicBuff, sizeof(magicJNG))!=0)
	{
		fclose(inFile);
		showErrorAndExit(strErrNoneJngInputFile, ERROR_NON_JNG_INPUT_FILE);
	}

	mng_handle hmng;
	mng_info mngInfo;
	mng_retcode retCode;

	memset(&mngInfo, 0, sizeof(mngInfo));

	mngInfo.file = inFile;
	hmng = mng_initialize(&mngInfo, mymngalloc, mymngfree, MNG_NULL);
	if (hmng == NULL)
	{
		fclose(inFile);
		showErrorAndExit(strErrDecodeInputFile, ERROR_DECODE_INPUT_FILE);
	}

	mng_setcb_errorproc(hmng, mymngerror);
	mng_setcb_openstream(hmng, mymngopenstream);
	mng_setcb_closestream(hmng, mymngclosestream);
	mng_setcb_readdata(hmng, mymngreadstream);

	mng_setcb_processheader(hmng, mymngprocessheader);
	mng_setcb_getcanvasline(hmng, mymnggetcanvasline);
	mng_setcb_gettickcount(hmng, mymnggettickcount);
	mng_setcb_settimer(hmng, mymngsettimer);
	mng_setcb_refresh(hmng, mymngimagerefresh);

	retCode = mng_read(hmng);
	if(retCode)
	{
		mng_cleanup(&hmng);
		fclose(inFile);
		showErrorAndExit(strErrDecodeInputFile, ERROR_DECODE_INPUT_FILE);
	}
	
	if(chunksOutDir!=NULL)
	{
		mngInfo.chunksOutDir = chunksOutDir;
		if(mng_iterate_chunks(hmng, 0, mymng_iteratechunk) != 0)
		{
			mng_cleanup(&hmng);
			fclose(inFile);
			showErrorAndExit(strErrDecodeInputFile, ERROR_DECODE_INPUT_FILE);
		}
	}

	if(mng_get_sigtype (hmng) != mng_it_jng)
	{
        mng_cleanup(&hmng);
		fclose(inFile);
		showErrorAndExit(strErrDecodeInputFile, ERROR_NON_JNG_INPUT_FILE);
	}

	mngInfo.timer = 2;
	retCode = mng_display(hmng);
	while((retCode == MNG_NEEDTIMERWAIT) && (mngInfo.timer <= 1))
	{
		retCode = mng_display_resume(hmng);
	}
	if((retCode != MNG_NOERROR) && (retCode != MNG_NEEDTIMERWAIT))
	{
		mng_cleanup(&hmng);
		showErrorAndExit(strErrDecodeInputFile, ERROR_DECODE_INPUT_FILE);
	}

	mng_cleanup(&hmng);
	fclose(inFile);

	outImage = mngInfo.image;
	mngInfo.image = NULL;
}

int handle_arg_file_name(struct command *self, void* param)
{
	*((const TCHAR**)param) = (const TCHAR*)self->arg;

	return 1;
}

int _tmain(int argc, _TCHAR* argv[])
{
	const TCHAR*	inFileName = NULL;
	const TCHAR*	outFileName = NULL;
	const TCHAR*	dumpChunksOutDir = NULL;

	command_t cmd;
	command_init(&cmd, argv[0], _T("1.0.3"));

	command_option(&cmd, _T("-i"), _T("--in <file_name>"), _T("input jng file."), handle_arg_file_name, &inFileName);
	command_option(&cmd, _T("-o"), _T("--out <file_name>"), _T("output tga file."), handle_arg_file_name, &outFileName);
	command_option(&cmd, _T("-d"), _T("--dump [out_dir]"), _T("dump chunks in separate files to specified directory (dumping only JHDR, IDAT, JDAA, JDAT chunnks)"), handle_arg_file_name, &dumpChunksOutDir);

	command_parse(&cmd, argc, argv);
	command_deinit(&cmd);

	Image* rgbImage = NULL;

	showMessage(strMsgStarting, inFileName);

	loadJNG(inFileName, rgbImage, dumpChunksOutDir);

	rgbImage->dumpImage(outFileName);

	showMessage(strMsgFinished);

	return 0;
}

#pragma warning(pop)